package com.d7024e.firstTest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import com.ekahau.common.sdk.EConnection;
import com.ekahau.common.sdk.EException;
import com.ekahau.common.sdk.EMsg;
import com.ekahau.common.sdk.EResponse;
import com.ekahau.engine.sdk.Device;
import com.ekahau.engine.sdk.EngineException;
import com.ekahau.engine.sdk.PositioningEngine;
import com.ekahau.engine.sdk.event.AbstractEngineEventListener;
import com.ekahau.engine.sdk.event.LocationEstimate;
import com.ekahau.engine.sdk.event.ScanEvent;
import com.ekahau.engine.sdk.event.ZoneEvent;

/**
 * Sample command line application for using EPE's event API.
 * 
 * This application allows the creation of two types of simple rules:
 * a "button rule" that produces an event from every button press on a tag,
 * and a "zone rule" that produces an event whenever a device moves to a new zone.
 * 
 * The application can be used to receive events in real time 
 * (using the <code>eventstream</code> API), or to look at the history of 
 * past events (using the <code>historylist</code> API).  
 * 
 * When the application is closed, it cleans up its rules and history from EPE
 * (using the <code>ruledelete</code> and <code>rulepurge</code> APIs),
 * so experimenting with it doesn't leave any garbage in EPE.
 * 
 * @author Ekahau, Inc.
 */
public class EventApplication {

	static {
		// Only log warnings
		System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
		System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
		System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "warn");
	}
	
	// Implementation of IEventListener for receiving new events from stream:
	static class EventListener extends AbstractEngineEventListener {
		private boolean isClosed = false;
		
		public void connectionClosed(Exception pReason) {
			System.err.println("Connection closed by server: " + pReason);
			// Notify waiters that connection was closed
			synchronized (this) {
				this.isClosed = true;
				notifyAll();
			}
		}
		
		// Wait until connection is closed or thread is interrupted
		synchronized void waitUntilClosed() throws InterruptedException {
			while (!isClosed) {
				wait();
			}
		}

		// Called when a new button event has been received
		public void newScanEvent(ScanEvent pEvent, Device pDevice, 
				LocationEstimate pLocation) {
			// Print something about the received event.
			String eventDesc = pEvent.toString() + " --- device: " + pDevice.toString();
			
			// pLocation can be null if the evaluation of the tag's location has been unsuccessful.
			if (pLocation != null)
				eventDesc += " --- location: " + pLocation.toString();
			
			System.out.println(eventDesc);
		}
		
		// Called when a new zone entered event has been received
		public void newZoneEnteredEvent(ZoneEvent pEvent, Device pDevice, 
				LocationEstimate pLocation) {
			// Print something about the received event.
			String eventDesc = pEvent.toString() + " --- device: " + pDevice.toString();
			
			// pLocation can be null if the evaluation of the tag's location has been unsuccessful.
			if (pLocation != null)
				eventDesc += " --- location: " + pLocation.toString();
			
			System.out.println(eventDesc);
		}
	}
	
	// Default values
	private static final String DEFAULT_HOSTNAME = "130.240.233.27";
	private static final int DEFAULT_PORT = 8550;
	private static final String DEFAULT_USERNAME = "admin";
	private static final String DEFAULT_PASSWORD = "admin";
	
	private static final String RULE_LABEL = "sdk.exampl";  // max 10 chars
	
	// Limit history list response to at most 100 events.
	private static final Integer HISTORY_LIST_LIMIT = new Integer(100); 
	
	// Configuration data object
	static class Config {
		String hostname = DEFAULT_HOSTNAME;
		int port = DEFAULT_PORT;
		String username = DEFAULT_USERNAME;
		String password = DEFAULT_PASSWORD;
	}
	
	// Connections to engine
	private static PositioningEngine mEngine;
	private static EConnection mEngineConnection;
	// Reader for getting user input
	private static BufferedReader mReader;
	
	// Print usage to stdout
	private static void usage() {
		System.out.println("Usage: java EventApplication [options]");
		System.out.println();
		System.out.println("Sample application for working with the Ekahau Positioning Engine event API.");
		System.out.println();
		System.out.println("Options:");
		System.out.println("  -h HOSTNAME     engine server hostname (default: \"" +
				DEFAULT_HOSTNAME + "\")");
		System.out.println("  -p PORT         engine server port (default: \"" +
				DEFAULT_PORT + "\")");
		System.out.println("  -u USERNAME     engine username (default: \"" +
				DEFAULT_USERNAME + "\")");
		System.out.println("  -w PASSWORD     password for the user (default: \"" +
				DEFAULT_PASSWORD + "\")");
		System.out.println("  --help          display this help and exit");
	}
	
	// Parse arguments to a Config object, return null if invalid args
	private static Config parseArgs(String[] args) {
		Config ret = new Config();
		// parse options
		int i;
		for (i = 0; i < args.length; ++i) {
			String opt = args[i];
			if (!opt.startsWith("-"))
				break;
			if (opt.equals("-h")) {
				if (i < args.length - 1) {
					ret.hostname = args[++i];
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else if (opt.equals("-p")) {
				if (i < args.length - 1) {
					String portStr = args[++i];
					try {
						ret.port = Integer.parseInt(portStr);
					} catch (NumberFormatException e) {
						System.err.println(portStr + " is not valid port.");
						return null;
					}
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else if (opt.equals("-u")) {
				if (i < args.length - 1) {
					ret.username = args[++i];
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else if (opt.equals("-w")) {
				if (i < args.length - 1) {
					ret.password = args[++i];
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else {
				System.err.println("Unknown option '" + opt + "'. Try --help for usage.");
			}
		}
		return ret;
	}
	
	// Print runtime menu to stdout
	private static void printMenu() {
		System.out.println("Commands: (the first character of a command is sufficient)");
		System.out.println("r(ule)    - create rule");
		System.out.println("e(vents)  - observe events");
		System.out.println("h(istory) - show event history");
		System.out.println("q(uit)    - exit the application");
		System.out.println();
		System.out.print("> ");
	}
	
	// Delete all example rules
	private static void deleteRules() throws EException {
		/* Get a list of all rules belonging to this example application
		   (identified by our label) */
		EResponse response = mEngineConnection.call(
				"/epe/eve/rulelist?label=" + RULE_LABEL);
		EMsg[] myRules = response.get();
		
		for (int i = 0; i < myRules.length; i++) {
			// Delete and purge each of the listed rules.
			mEngineConnection.call("/epe/eve/ruledelete?ruleid=" + 
					myRules[i].get("ruleid"));
			// Purge is needed to clean up also the event history related to the rule.
			mEngineConnection.call("/epe/eve/rulepurge?ruleid=" + 
					myRules[i].get("ruleid"));
		}
	}
	
	// Create a simple example rule
	private static void createRule() throws EException {
		EMsg params = new EMsg();				
		System.out.println("Creating a new event rule.");
		System.out.println();
		
		try {
			System.out.print("Enter a name for the rule: ");
			params.add("name", mReader.readLine());
			System.out.print("Enter a description for the rule: ");
			params.add("description", mReader.readLine());
			System.out.println("What kind of events should be tracked? " +
					"Give 'button' (button pressed) or 'zone' (zone entered):");
			boolean ok = false;
			while (!ok) {
				String ruleType = mReader.readLine();
				if ("button".equalsIgnoreCase(ruleType)) {
					// Scan reasons 1, 6 and 8 stand for different buttons.
					params.add("scanreason", "1,6,8");
					ok = true;
				}
				else if ("zone".equalsIgnoreCase(ruleType)) {
					// Request an event from entering any zone. 
					params.add("enterzoneid", "any");
					ok = true;
				}
				else {
					System.out.println("Invalid event type: '" + ruleType + "'. " +
							"Give either 'button' or 'zone':");
				}
			}			
		} catch (IOException e) {
			e.printStackTrace(System.err);
			return;
		}
		
		// All rules created here get a common label.
		params.add("label", RULE_LABEL);		
		
		// Call rulecreate API with these parameters.
		mEngineConnection.call("/epe/eve/rulecreate", params);
		System.out.println("Rule creation completed.");
	}
	
	// Receive events in real time
	private static void observeEvents() throws EException, EngineException {
		// Get a list of rules that this application "owns".
		EResponse response = mEngineConnection.call(
				"/epe/eve/rulelist?label=" + RULE_LABEL);
		EMsg[] myRules = response.get();
		
		if (myRules.length == 0) {
			System.out.println("You need to first create at least one rule " +
					"to be able to receive events.");
			return;
		}
		
		// Print something about the rules that we have.
		System.out.println("Observing events using the following rules:");
		System.out.println("name\tdescription");
		System.out.println("====\t===========");
		for (int i = 0; i < myRules.length; i++) {
			System.out.println(myRules[i].get("name") + 
					"\t" + myRules[i].get("description"));
		}
		System.out.println("--------------------------------------------");
		System.out.println("Press enter to stop and return to main menu.");
		System.out.println("--------------------------------------------");
		
		// Start listening to the stream.
		EventListener l = new EventListener();
		mEngine.addEventListener(l, null, RULE_LABEL, false);
		mEngine.startTracking();
		
		try {
			mReader.readLine();
		} catch (IOException e) {
			// Ignored
		}
		System.out.println("Waiting for the event stream to close...");
		mEngine.stopTracking();
		mEngine.removeAllListeners();	
				
		try {
			l.waitUntilClosed();
		} catch (InterruptedException e) {
			// Ignored
		}
		System.out.println("Event stream closed");
	}
	
	// Show the complete history of events (up to a limit of 100 items) 
	// from the example rules
	private static void showHistory() throws EngineException {
		List events = mEngine.getEventHistory(null, null, RULE_LABEL, 
				null, null, null, null, null, null, HISTORY_LIST_LIMIT);
		if (events.size() == 0) {
			System.out.println("No history found for the current rules.");
			return;
		}
		// Print something simple about each event.
		for (int i = 0; i < events.size(); i++) {
			System.out.println(events.get(i).toString());
		}
	}
	
	// Execute a user command
	private static void executeCommand(char command) 
			throws EException, EngineException {
		switch (command) {
			case 'r':  // create a new rule
				createRule();
				break;
			case 'e':  // observe events
				observeEvents();
				break;
			case 'h':  // show history
				showHistory();
				break;
			default:
				System.err.println("Unknown command '" + command + "'.");
		}
	}
	
	// Run command from configuration, return status
	private static int run(Config cfg) {
		// Create objects needed for the communication with EPE.
		mEngine = new PositioningEngine(
				cfg.hostname, cfg.port, cfg.username, cfg.password);
		mEngineConnection = new EConnection(cfg.hostname, cfg.port);
		mEngineConnection.setUserCredentials(cfg.username, cfg.password);
		
		mReader = new BufferedReader(new InputStreamReader(System.in));
		printMenu();
		
		try {
			String command = mReader.readLine();
			
			while (command != null && 
				(command.length() == 0 || command.charAt(0) != 'q')) {
				
				if (command.length() > 0)
					executeCommand(command.charAt(0));
				
				System.out.println();
				printMenu();
				
				command = mReader.readLine();
			}
			
			return 0;
			
		} catch (EException e) {
			e.printStackTrace(System.err);
			return 1;
		} catch (EngineException e) {
			e.printStackTrace(System.err);
			return 1;
		} catch (IOException e) {
			e.printStackTrace(System.err);
			return 1;
		} finally {
			// Clean up by deleting all rules created during the session.
			try {
				deleteRules();
			} catch (EException e) {
				e.printStackTrace(System.err);
				return 1;
			}
		}
	}	
	
	// Entry point for the program
	public static void main(String[] args) {		
		if (args.length > 0 && args[0].equals("--help")) {
			usage();
			System.exit(0);
		}
		
		Config cfg = parseArgs(args);
		if (cfg == null) {
			System.exit(1);
		}
		
		int ret = run(cfg);
		System.exit(ret);
	}
}
